package org.chispa.api;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;

import org.chispa.api.query.QuerySparqlWrapper;
import org.chispa.api.query.QueryWrapperFactory;
import org.chispa.api.util.ExpressionLanguage;
import org.chispa.api.util.NoSuitableVisitorException;
import org.chispa.api.visitors.NumberPVFactory;
import org.chispa.api.visitors.SimpleQueryVisitor;
import org.chispa.api.visitors.SimpleStringPVFactory;

import com.google.common.base.Joiner;

public class ChispaManager {
	
	
	public ChispaManager(){fact=QueryWrapperFactory.getInstance();};
	
	private QueryWrapperFactory fact;
	
	private QuerySparqlWrapper wrap;
	
	private static final String INDEX="_index";
	
	private ArrayList<String> declareIndex(String var,ArrayList<String> tokens)
	{
		ArrayList<String> declared=new ArrayList<String>();
		boolean wherePassed=false;
		for(String token:tokens)
		{
			if(token.equals(var) && wherePassed)
			{
				String verb=declared.get(declared.size()-1)+INDEX;
				String obj=declared.get(declared.size()-2);
				declared.add(token);
				declared.add(".");
				declared.add(obj);
				declared.add(verb);
				declared.add(var+INDEX);
				declared.add(".");
			}
			else if(token.equalsIgnoreCase("WHERE"))
			{
				declared.add(token);
				wherePassed=true;
			}
			else
				declared.add(token);
		}
		return declared;
	}
	
	public String beforeSend(String queryString) throws NoSuitableVisitorException
	{
		wrap=(QuerySparqlWrapper) fact.createWrapperFromString(queryString);
		NumberPVFactory factor=new NumberPVFactory();
		String proc=wrap.accept(new SimpleQueryVisitor(new SimpleStringPVFactory(),factor , factor));
		Scanner scan=new Scanner(proc);
		ArrayList<String> tokens=new ArrayList<String>();
		Set<String> done=new HashSet<String>();
		while(scan.hasNext())
		{
			String token=scan.next();
			if(ExpressionLanguage.matchesDecr(token))
			{
				scan.next();
				String enc=scan.next();
				String verb=tokens.get(tokens.size()-1)+INDEX;
				tokens.add(enc);
				tokens.add(";");
				tokens.add(verb);
				scan.next();
				String index=scan.next();
				tokens.add(index);
				tokens.add(".");
				scan.next();
			}
			
			else if(ExpressionLanguage.matchesIndex(token))
			{
				scan.next();
				String var=scan.next();
				String[] vars=var.split(":");
				String indVar = "";
				for(String variable:vars)
				{
					indVar=variable+INDEX;
					if(!done.contains(variable))
					{
						tokens=declareIndex(variable,tokens);
						done.add(variable);
					}
				}
			
				if(token.endsWith(ExpressionLanguage.STRING_NAME))
				{
					scan.next();
					done.add(var);
					tokens.add("(");
					buildStringFilter(tokens,scan,indVar,1,0);
					tokens.add(")");
				}
				else
				{
					tokens.add("(");
					tokens=buildNumFilter(tokens,scan,var,1,0,done);
					tokens.add(")");
				}
			}
			else
			{
				tokens.add(token);
			}
		}
		return Joiner.on(" ").skipNulls().join(tokens);
			
	}
	
	private ArrayList<String> buildNumFilter(ArrayList<String> tokens, Scanner scan,
			String var, int lbac, int rbac,Set<String> done) {
		String next=scan.next();
		if(next.equals("{"))
		{
			String[] vars=var.split(":");
			String text;
			while(scan.hasNext())
			{
				text=scan.next();
				if(text.equals("}"))
				{
					tokens.remove(tokens.size()-1);
					next=(scan.next());
					break;
				}
				if(!text.equals(","))
				{
					String[]numbers=text.split(":");
					if(numbers.length>1)
						tokens.add("(");
					for(int i=0;i<Math.min(vars.length,numbers.length);i++)
					{
						if(!done.contains(vars[i]))
						{
							tokens=declareIndex(vars[i],tokens);
							done.add(vars[i]);
						}
						tokens.add(vars[i]+INDEX);
						tokens.add("=");
						tokens.add(numbers[i]);
						if(i!=Math.min(vars.length,numbers.length)-1)
							tokens.add("&&");
					}
					if(numbers.length>1)
						tokens.add(")");
					tokens.add("||");
				}
			}
		}
		if(next.equals(")"))
		{
			rbac++;
			if(lbac==rbac)
				return tokens;
			tokens.add(next);
			tokens=buildNumFilter(tokens,scan,var,lbac,rbac,done);
			return tokens;
		}
		else if(next.equals("("))
		{
			tokens.add(next);
			lbac++;
			tokens=buildNumFilter(tokens,scan,var,lbac++,rbac,done);
			return tokens;
		}
		
		else if(next.equalsIgnoreCase("||") || next.equalsIgnoreCase("&&"))
		{
			tokens.add(next);
			return buildNumFilter(tokens,scan,var,lbac,rbac,done);
		}
		
		else
		{
			return buildNumFilter(tokens,scan,next,lbac,rbac,done);
		}
		
		
	}

	private void buildStringFilter(ArrayList<String> tokens,Scanner scan,String var,int lbac,int rbac)
	{
		String start=scan.next();
		String index="";
		if(start.equals(")"))
		{
			rbac++;
			if(lbac==rbac)
				return;
			tokens.add(start);
			buildStringFilter(tokens,scan,var,lbac,rbac);
			return;
		}
		else if(start.equals("("))
		{
			tokens.add(start);
			lbac++;
			buildStringFilter(tokens,scan,var,lbac++,rbac);
			return;
		}
		if(start.equalsIgnoreCase("LIKE"))
		{
			index=partial(scan.next());
		}
		else if(start.equalsIgnoreCase("NOT"))
		{
			scan.next();
			tokens.add("!");
			index=partial(scan.next());
		}
		else if(start.equalsIgnoreCase("||") || start.equalsIgnoreCase("&&"))
		{
			tokens.add(start);
			buildStringFilter(tokens,scan,var,lbac,rbac);
			return;
		}
		else
		{
			index=start;
		}
		tokens.add("regex");
		tokens.add("(");
		tokens.add(var);
		tokens.add(",");
		tokens.add(index);
		tokens.add(")");
		buildStringFilter(tokens,scan,var,lbac,rbac);
		return;
	}
	
	private String partial(String orig)
	{
		char[] chars=orig.toCharArray();
		StringBuilder build=new StringBuilder();
		for(char chara:chars)
		{
			if(chara=='0')
			{
				build.append("(0|1)");
			}
			else
			{
				build.append(chara);
			}
		}
		return build.toString();
	}

	public Map<String, String> afterQuery(Map<String, String> varValues,List<String> bindingNames)  {
		try {
			for(String varName:bindingNames)
			{
				String value=varValues.get(varName);
				if(wrap.isSecure(varName))
				{
					value= wrap.onDecryptText(value.replace("\"", ""));
				}
				varValues.put(varName, value);
			}
			if(!wrap.matches(varValues,bindingNames))
				return null;
			return varValues;
			
		} catch (NoSuitableVisitorException e) {
		}
		return null;
	}

}
